package com.model.demo.spring.framework.context;

import com.model.demo.spring.framework.beans.MyBeanFactory;
import com.model.demo.spring.framework.beans.MyBeanWrapper;
import com.model.demo.spring.framework.beans.config.MyBeanDefinition;
import com.model.demo.spring.framework.beans.support.MyBeanDefinitionReader;
import com.model.demo.spring.framework.beans.support.MyDefaultListableBeanFactory;
import com.model.demo.spring.mini.mvcframework.annotation.MyAutowired;
import com.model.demo.spring.mini.mvcframework.annotation.MyController;
import com.model.demo.spring.mini.mvcframework.annotation.MyService;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

//ApplicationContext最底层的容器实现
//DefaultListableBeanFactory容器默认实现
public class MyApplicationContext extends MyDefaultListableBeanFactory implements MyBeanFactory {

    private String [] configLocations ;

    private MyBeanDefinitionReader reader ;

    private final Map<String, MyBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16);

    private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);

    public MyApplicationContext(){}

    public MyApplicationContext(String... configLocations){
        this.configLocations = configLocations;

        refresh();
    }

    //依赖注入，从这里开始，通过读取 BeanDefinition 中的信息
    //然后，通过反射机制创建一个实例并返回
    //Spring 做法是，不会把最原始的对象放出去，会用一个 BeanWrapper 来进行一次包装
    //装饰器模式：
    //1、保留原来的 OOP 关系
    //2、我需要对它进行扩展，增强（为了以后 AOP 打基础）
    public Object getBean(String beanName) {
        try {
            //分两步避免循环依赖
            //Question
                //class A {B b;}
                //class B {A a;}
                //先初始化A,注入B时可根据注册信息BeanDefinition判断是否是单例，是就从单例缓存中获取注入，否就根据类名new一个注入
            //1.初始化
            MyBeanDefinition beanDefinition = super.beanDefinitionMap.get(beanName);
            MyBeanWrapper beanWrapper = instantiateBean(beanDefinition);
            if(beanWrapper == null){return null;}
            //把BeanWrapper注册到IOC容器中
            this.factoryBeanInstanceCache.put(beanName, beanWrapper);
            //2.注入
            populateBean(beanWrapper);
            return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

    protected MyBeanWrapper instantiateBean(MyBeanDefinition beanDefinition) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //1.目标实例化对象的类名
        String beanClassName = beanDefinition.getBeanClassName();
        //2.反射创建实例
        Object bean ;
        //单例对象缓存
        //Question
        //要判断map里面是否有：类名首字母小写或类名注册的对象，
        //而beanName具备这两种情况，判断准确，不会遗漏存放
        if(this.factoryBeanObjectCache.containsKey(beanClassName)){
            bean = this.factoryBeanObjectCache.get(beanClassName);
        }else{
            Class<?> beanClass = Class.forName(beanClassName);
            bean = beanClass.newInstance();
            this.factoryBeanObjectCache.put(beanClassName, bean);
            this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(), bean);
        }
        //3.把实例对象封装到BeanWrapper中
        MyBeanWrapper beanWrapper = new MyBeanWrapper(bean);

        return beanWrapper;
    }

    //将Bean属性设置到生成的实例对象上
    protected void populateBean(MyBeanWrapper bw) throws IllegalAccessException {
        Object wrapperInstance = bw.getWrapperInstance();
        Class<?> wrappedClass = bw.getWrappedClass();
        if(!wrappedClass.isAnnotationPresent(MyController.class)
            && !wrappedClass.isAnnotationPresent(MyService.class)){
            return ;
        }

        Field[] fields = wrappedClass.getDeclaredFields();

        for (Field field : fields) {
           if(!field.isAnnotationPresent(MyAutowired.class)){continue;}
            MyAutowired autowired = field.getAnnotation(MyAutowired.class);
            String autowiredName = autowired.value().trim();
            if("".equals(autowiredName)){
                autowiredName = field.getType().getName();
            }
            field.setAccessible(true);
            if(this.factoryBeanInstanceCache.get(autowiredName) == null){continue;}
            field.set(wrapperInstance, this.factoryBeanInstanceCache.get(autowiredName).getWrapperInstance());
        }


    }

    public Object getBean(Class<?> clazz){
        return getBean(clazz.getName());
    }


    //一键启动
    public void refresh() {
        //初始化IOC
        //定位、加载、注册
        //1、定位，定位配置文件
        reader = new MyBeanDefinitionReader(this.configLocations);

        //2、加载配置文件，扫描相关的类，把它们封装成BeanDefinition
        List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        //3、注册，把配置信息放到容器里面(伪IOC容器)
        doRegisterBeanDefinition(beanDefinitions);

        //4、把不是延时加载的类，有提前初始化
        doAutowrited();

    }

    private void doAutowrited() {
        if(super.beanDefinitionMap.isEmpty()){return;}
        for (Map.Entry<String, MyBeanDefinition> entry : super.beanDefinitionMap.entrySet()) {
            if(!entry.getValue().isLazyInit()){
                getBean(entry.getKey());
            }
        }
    }

    private void doRegisterBeanDefinition(List<MyBeanDefinition> beanDefinitions) {
        for (MyBeanDefinition beanDefinition : beanDefinitions) {
                try {
                    if(super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                        throw new Exception("The " + beanDefinition.getFactoryBeanName() + "is exist!!!");
                    }
                    super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
                } catch (Exception e) {
                    e.printStackTrace();

            }
        }
    }

    public String[] getBeanDefinitionNames(){
        return super.beanDefinitionMap.keySet().toArray(new String[getBeanDefinitionCount()]);
    }

    public int getBeanDefinitionCount(){
        return super.beanDefinitionMap.size();
    }

    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        chars[0] += 32;
        return  String.valueOf(chars);
    }

    public Properties getConfig(){
        return reader.getConfig();
    }

}
